﻿#region namespaces

using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Threading;
using System.Xml.Linq;
using Tools.BaseX;
using ZOIL.DataModel;

#endregion

namespace ZOIL.DataBackend.BaseX
{
    public sealed partial class Database
    {
        #region Watching

        private void StartWatchingEventsFromDatabase()
        {
            _client.EventTriggered += OnDatabaseEventTriggered;
            _client.WatchEvent(DbConfig.EventName, DbConfig.DatabaseName);
        }

        private void OnDatabaseEventTriggered(object sender, BaseXEventArgs e)
        {
            if (string.Equals(e.EventName, DbConfig.EventName))
            {
                HandleEvent(e.Results);
            }
        }

        private void StopWatchingEventsFromDatabase()
        {
            _client.EventTriggered -= OnDatabaseEventTriggered;
            _client.UnwatchEvent(DbConfig.EventName, DbConfig.DatabaseName);
        }

        #endregion

        #region Event

        private readonly object _sendEventLock = new object();

        private void SendEvent(XElement content)
        {
            _client.TriggerEventSynchronously(DbConfig.EventName, content.ToString(), null, DbConfig.DatabaseName);
        }

        private static class EventType
        {
            /*
             * <Add Id="...">
             *  <DbObj zoil:Path="My.Namespace.DbObj" ... />
             *  <VP zoil:Path="My.VpNamespace.VP" ... />
             * </Add>
             */
            public const string Add = "Add";
            /*
             * <Delete Id="..." />
             */
            public const string Delete = "Delete";
            /*
             * <Update Id="...">
             *  <DbObj zoil:Path="My.Namespace.DbObj" ... />
             * </Update>
             */
            public const string Update = "Update";
            /*
             * <Move Id="...">
             *  <VP zoil:Path="My.VpNamespace.VP" ... />
             * </Move>
             */
            public const string Move = "Move";
            /*
             * <Message>...<Message>
             */
            public const string Message = "Message";
        }

        #endregion

        #region Handle Remote Events

        private void HandleEvent(string content)
        {
            //TODO: Fehler noch fangen und loggen
            HandleEvent(XElement.Parse(content));
        }

        private void HandleEvent(XElement content)
        {
            string eventType = content.Name.LocalName;
//            Debug.WriteLine(string.Format("Got event of type {0}", eventType));
            switch (eventType)
            {
                case EventType.Add:
                    HandleEventAdd(content);
                    break;
                case EventType.Delete:
                    HandleEventDelete(content);
                    break;
                case EventType.Update:
                    HandleEventUpdate(content);
                    break;
                case EventType.Move:
                    HandleEventMove(content);
                    break;
                case EventType.Message:
                    HandeEventMessage(content);
                    break;
                default:
                    throw new ArgumentException(string.Format("No valid event type! ({0})", eventType), "content");
            }
        }

        private void HandleEventAdd(XElement evt)
        {
            Debug.Assert(evt != null, "evt != null");
            var id = long.Parse(evt.Attribute("Id").Value);

            //überprüfen ob schon local vorhanden
            //sollte nicht vorkommen
            Debug.Assert(!_localDbObjects.Contains(id));

            //xml holen
            var objXml = evt.Elements().First();
            var propXml = evt.Elements().Skip(1).Single();

            //deserialisieren
            var obj = (IPersistable) SerializationHelper.Deserialize(objXml, DbConfig);
            var prop = (IVisualProperties) SerializationHelper.Deserialize(propXml, DbConfig);

            //überprüfen ob local angenommen werden soll
            if (DbConfig.AcceptRemotelyCreatedObjectCondition == null ||
                !DbConfig.AcceptRemotelyCreatedObjectCondition(new DatabaseObjectActionEventArgs(obj, prop)))
            {
                //nicht annehmen -> ignorieren
                Logger.Trace("Ignoring remotely added object with id {0}", id);
                return;
            }

            //in Buffer rein
            StoreLocally(ref obj, ref prop);

            //events auslösen
            {
                var orc = obj as INotifyObjectRemotelyCreated;
                if (orc != null)
                {
                    orc.OnRemotelyCreated();
                }
            }
            {
                var orc = prop as INotifyObjectRemotelyCreated;
                if (orc != null)
                {
                    orc.OnRemotelyCreated();
                }
            }

            OnObjectRemotelyCreated(new DatabaseObjectActionEventArgs(obj, prop));
        }

        private void HandleEventUpdate(XElement evt)
        {
            Debug.Assert(evt != null, "evt != null");
            var id = long.Parse(evt.Attribute("Id").Value);
            //wenn nicht local vorhanden: ignorieren
            if (!_localDbObjects.Contains(id))
            {
                Logger.Trace("Ingoring update for id {0} because it does not exist on this client", id);
                return;
            }

            var objXml = evt.Elements().Single();
            var obj = (IPersistable) SerializationHelper.Deserialize(objXml, DbConfig);

            var existing = _localDbObjects.Get(id);
            var existingObj = existing.DatabasePersistable;

            //Object updaten
            var cu = existingObj as ICustomUpdateable;
            if (cu != null)
            {
                //object darf sich selbst um update kümmern
                cu.OnUpdate(obj);
            }
            else
            {
                //neue Werte in existierendes Object einpflanzen -> Referenz von Object bleibt gleich
                obj.GetType().ExchangeFieldsOnFirstLevel(existingObj, obj);
            }

            //und signalisieren dass upgedated
            var up = existingObj as IRemoteUpdateable;
            if (up != null)
            {
                up.OnRemoteUpdated();
            }
            //und allgemein signalisieren
            OnObjectRemotelyUpdated(new DatabaseObjectActionEventArgs(existing.DatabasePersistable, existing.VisualProperties));
        }

        private void HandleEventMove(XElement evt)
        {
            Debug.Assert(evt != null, "evt != null");
            var id = long.Parse(evt.Attribute("Id").Value);

            //muss local vorhanden sein damit interessant
            if (!_localDbObjects.Contains(id))
            {
                Logger.Trace("Ingoring move for id {0} because it does not exist on this client", id);
                return;
            }

            var propXml = evt.Elements().Single();
            var prop = (IVisualProperties) SerializationHelper.Deserialize(propXml, DbConfig);

            var existing = _localDbObjects.Get(id);
            var existingProp = existing.VisualProperties;

            //Object updaten
            var cu = existingProp as ICustomUpdateable;
            if (cu != null)
            {
                //object darf sich selbst um update kümmern
                Application.Current.Dispatcher.Invoke(new Action(() => cu.OnUpdate(prop)));
//                cu.OnUpdate(prop);
            }
            else
            {
                //neue Werte in existierendes Object einpflanzen -> Referenz von Object bleibt gleich
                prop.GetType().ExchangeFieldsOnFirstLevel(existingProp, prop);
            }

            //und signalisieren dass upgedated
            var up = existingProp as IRemoteUpdateable;
            if (up != null)
            {
                Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(up.OnRemoteUpdated));
//                up.OnRemoteUpdated();
            }
            //und allgemein signalisieren
            OnObjectRemotelyMoved(new DatabaseObjectActionEventArgs(existing.DatabasePersistable, existing.VisualProperties));
        }

        private void HandleEventDelete(XElement evt)
        {
            var id = long.Parse(evt.Attribute("Id").Value);
            DatabaseObjectManager.DbItem dbi;
            if (DeleteLocally(id, out dbi, false))
            {
                OnObjectRemotelyDeleted(dbi.ToEventArgs());
                ResetDatabaseId(dbi);
                Logger.Trace("Id {0} deleted", id);
            }
            else
            {
                Logger.Trace("Ignoring delete for id {0}", id);
            }
        }

        private void HandeEventMessage(XElement evt)
        {
            var message = evt.Value;
            Logger.Trace("Message received: {0}", message);
            OnMessageReceived(message);
        }

        #endregion

        #region Message

        /// <summary>
        ///     Send a message to all other clients
        ///     !synchron: send the message NOW
        /// </summary>
        /// <param name="message"> </param>
        public void SendMessage(string message)
        {
            //wird sofort ausgeführt
            var evt = new XElement(EventType.Message, message);
            SendEvent(evt);
        }

        #endregion
    }
}